package com.beta.resource.protocol;

import java.io.IOException;

import javax.websocket.Session;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSON;
import com.beta.resource.model.Metal;
import com.beta.resource.model.UserFile;
import com.beta.resource.services.UserEnvService;
import com.beta.resource.utils.BaseFile;
import com.beta.resource.utils.BaseShell;
import com.beta.resource.utils.BaseString;
import com.beta.resource.utils.onStatusChange;

public class Mecha {
    public static final int TOKEN_LENGTH = 12;
    public static final int RESPOND_MASK_INVALID = 0x8000;
    public static final int RESPOND_MASK_SUCCESS = 0x1000;
    public static final int RESPOND_MASK_FAILED = 0x2000;
    public static final int RESPOND_MASK_RUNNING = 0x4000;
    public static final int RESPOND_MASK = 0xF000;
    public static final int BROADCAST_MASK = 0x8000;
    public static final String CHARSET_DEFAULT = "UTF-8";
    private static final String RESOURCE_HELPINFO = "help.txt";
    private static final String[] ALLOWED_CMD = {"ping", "uname", "pwd", "ls", "whoami", "cat"};

    public static final String VERSION_INFO = "Mecha Protocol v1.0";
    public static final String INVALID_INFO = "REQUEST INVALID";
    public static final String NOTSUPPORT_INFO = "NOT SUPPORT YET";

    public static final String CMD_HELP = "help";

    private String mSuid = null;
    private Session mSession = null;
    private UserEnvService mEnvService = null;
    private Logger log = LoggerFactory.getLogger(getClass());

    private onStatusChange mListener = new onStatusChange() {
        private Metal createMetal(Object data) {
            Metal metal = null;
            if (data instanceof Metal) {
                metal = (Metal) data;
            } else {
                metal = Metal.createNew(null);
            }
            return metal;
        }

        private boolean fillAndSend(Metal metal, int mime, String body) {
            metal.cmd |= mime;
            metal.body = body;
            boolean ret = false;
            try {
                ret = send(metal);
            } catch (IOException e) {
                log.error(e.getMessage());
                e.printStackTrace();
                ret = false;
            }
            if (body != null) {
                metal.index += 1;
            }
            return ret;
        }

        public boolean onLoad(int mime, String buffer, Object data) {
            boolean ret = true;
            Metal metal = createMetal(data);
            if(buffer == null){
                return fillAndSend(metal, mime, null);
            }
            String body = null;
            int length = buffer.length();
            int startindex = 0;
            int endindex = Math.min(length,Metal.PACKAGE_BODY_MAX_LENGTH);
            do{
                body = buffer.substring(startindex, endindex);
                startindex = endindex;
                endindex += Metal.PACKAGE_BODY_MAX_LENGTH;
                endindex = Math.min(length, endindex);
                if(!fillAndSend(metal, mime, BaseString.encodeBase64(body, null))){
                    ret = false;
                    break;
                }
            }while(startindex < length);
            return ret;
        }

        @Override
        public boolean onLoad(int mime, byte[] buffer, int length, Object data) {
            return onLoad(mime, BaseString.translateEncode(buffer, length, CHARSET_DEFAULT), data);
        }
    };

    public Mecha(String suid) {
        log.debug("New Mecha Session for:"+suid);
        mSuid = suid;
    }

    public boolean send(Metal metal) throws IOException {
        boolean ret = (metal != null && mSession.isOpen());
        if (ret) {
            mSession.getBasicRemote().sendText(JSON.toJSONString(metal));
        }
        return ret;
    }

    public void send(String mesg) {
        Metal metal = Metal.createNew(mSuid);
        metal.index = 0;
        metal.cmd = BROADCAST_MASK;
        metal.body = mesg;
        try {
            send(metal);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void setSession(Session session) {
        mSession = session;
    }

    public void setEnvService(UserEnvService service) {
        mEnvService = service;
    }

    public Session getSession() {
        return mSession;
    }

    private Metal authRequest(String json) {
        log.debug("authRequest:"+json);
        Metal request = JSON.parseObject(json, Metal.class);
        request.parse();
        if (!mSuid.equals(request.suid) || !mEnvService.verifyToken(request.suid, request.token)) {
            request.cmd |= RESPOND_MASK_INVALID;
        }
        log.debug(String.format("%s auth request(%s) | mecha token(%s) | ret:%s", mSuid, request.suid, request.token, request.cmd));
        return request;
    }

    public Metal doRespond(String json) {
        Metal request = authRequest(json);
        Metal pkg = null;
        if ((request.cmd & RESPOND_MASK) == RESPOND_MASK_INVALID) {
            pkg = doInvalid(request);
        } else {
            switch (request.method) {
                case Metal.REQUEST_METHOD_CREATE:
                    pkg = doCreate(request);
                    break;
                case Metal.REQUEST_METHOD_DELETE:
                    break;
                case Metal.REQUEST_METHOD_QUERY:
                    pkg = doQuery(request);
                    break;
                case Metal.REQUEST_METHOD_UPDATE:
                    break;
            }
        }
        return pkg;
    }

    private Metal doCreate(Metal request) {
        Metal metal = request.copy();
        if (request.type == Metal.REQUEST_TYPE_FILE) {
            String[] emt = ((String) request.body).split("|");
            try{
                UserFile ufile = mEnvService.loadFile(request.suid, Integer.parseInt(emt[0]), null, null);
                String msg = BaseString.decodeBase64(emt[1], "");
                if (ufile != null) {
                    BaseFile.appendFile(ufile.getDecodePath(), msg.getBytes(), -1);
                }
            }catch(IOException e){
                e.printStackTrace();
                metal.cmd |= RESPOND_MASK_FAILED;
                metal.body= e.getMessage();
            }
        }
        return metal;
    }

    private Metal doQuery(Metal request) {
        log.debug("doQuery:" + request.type);
        Metal metal = request.copy();
        try {
            switch (request.type) {
                case Metal.REQUEST_TYPE_VERSION:
                    metal.cmd |= RESPOND_MASK_SUCCESS;
                    metal.index = 0;
                    metal.body = BaseString.encodeBase64(VERSION_INFO, null);
                    break;
                case Metal.REQUEST_TYPE_FILE:
                    if (request.body instanceof String) {
                        String path = BaseString.decodeBase64((String) request.body, null);
                        log.debug("query file:" + path);
                        metal.cmd |= RESPOND_MASK_RUNNING;
                        if (!BaseFile.loadFile(path, mListener, metal)) {
                            mEnvService.loadFile(request.suid, Integer.parseInt(path), mListener, metal);
                        }
                        cleanRespon(metal);
                        metal.cmd |= RESPOND_MASK_SUCCESS;
                        metal.body = null;
                    } else {
                        metal.cmd |= RESPOND_MASK_FAILED;
                        metal.body = BaseString.encodeBase64(INVALID_INFO, null);
                    }
                    break;
                case Metal.REQUEST_TYPE_CMD:
                    metal.cmd |= RESPOND_MASK_RUNNING;
                    doCMD(request, metal);
                    break;
            }
        } catch (Exception e) {
            e.printStackTrace();
            metal.cmd |= RESPOND_MASK_FAILED;
            metal.body = BaseString.encodeBase64(e.toString(), null);
        }
        log.debug("doQuery end:" + metal);
        return metal;
    }

    private boolean isAllowedCMD(String cmdstr){
        boolean allowed = false;
        if(cmdstr.contains(">")){
            return allowed;
        }
        String[] emt = cmdstr.split(";");
        for(String cmd : emt){
            cmd = cmd.trim().split(" ")[0];
            for(int i =0; i< ALLOWED_CMD.length; i++){
                if(cmd.equals(ALLOWED_CMD[i])){
                    allowed = true;
                    break;
                }
            }
            if(allowed){
                break;
            }
        }
        return allowed;
    }

    private Metal doCMD(Metal request, Metal respond) {
        log.debug("doCMD");
        try {
            String cmd = BaseString.decodeBase64((String) request.body, "");
            log.debug(cmd);
            if (cmd.equals(CMD_HELP)) {
                BaseFile.loadResourceFile(RESOURCE_HELPINFO, mListener, respond);
            } else if (cmd.isEmpty()) {
                cleanRespon(respond);
                respond.cmd |= RESPOND_MASK_FAILED;
                respond.body = BaseString.encodeBase64(NOTSUPPORT_INFO, null);
            } else if(isAllowedCMD(cmd)){
                BaseShell.exec(cmd, mListener, respond);
            }
            if ((respond.cmd & RESPOND_MASK) == RESPOND_MASK_RUNNING) {
                cleanRespon(respond);
                respond.cmd |= RESPOND_MASK_SUCCESS;
                respond.body = null;
            }
        } catch (IOException e) {
            e.printStackTrace();
            if (!e.getClass().toString().endsWith("ExecuteException")) {
                respond.cmd |= RESPOND_MASK_FAILED;
                respond.body = BaseString.encodeBase64(NOTSUPPORT_INFO, null);
            }
        }
        
        log.debug("doCMD end:" + respond);
        return respond;
    }

    private Metal doInvalid(Metal request) {
        log.debug("doInvalid");
        Metal metal = request.copy();
        metal.cmd = (metal.cmd - RESPOND_MASK_INVALID) | RESPOND_MASK_FAILED;
        metal.body = BaseString.encodeBase64(INVALID_INFO, null);
        return metal;
    }

    private void cleanRespon(Metal metal) {
        metal.cmd &= 0xFFF;
    }
}
